--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node>
+
+ <!--
+ net.hadess.PowerProfiles:
+ @short_description: Power Profiles daemon
+
+ The power-profiles-daemon API is meant to be used by parts of the OS or
+ desktop environment to switch system power profiles based on user choice,
+ or user intent.
+
+ OS components would typically use the "Profiles" property to construct
+ their UI (2 or 3 profiles available), and monitor the "ActiveProfile"
+ and the "PerformanceInhibited" properties to update that UI. The UI
+ would try to set the "ActiveProfile" property if the user selected
+ a different one.
+
+ Note that the reason why the project exists and how it is different from
+ existing projects is explained <ulink href=" https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/blob/master/README.md">
+ in the project's README file</ulink>.
+
+ The object path will be "/net/hadess/PowerProfiles".
+ -->
+ <interface name="net.hadess.PowerProfiles">
+ <!--
+ ActiveProfile:
+
+ The type of the currently active profile. It might change automatically
+ if the "performance" profile was selected but it got inhibited, in which
+ case the "PerformanceInhibited" property will reflect the reason.
+ -->
+ <property name="ActiveProfile" type="s" access="readwrite"/>
+
+ <!--
+ PerformanceInhibited:
+
+ This will be set if the performance power profile is unavailable, with
+ the value being used to identify the reason for unavailability. As new
+ reasons can be added, it is recommended that front-ends show a generic
+ reason if they do not recognise the value. Possible values are:
+ - "lap-detected" (the computer is sitting on the user's lap)
+ - "high-operating-temperature" (the computer is close to overheating)
+ - "" (the empty string, if not inhibited)
+ -->
+ <property name="PerformanceInhibited" type="s" access="read"/>
+
+ <!--
+ Profiles:
+
+ An array of key-pair values representing each profile. The key named
+ "Driver" (s) identifies the power-profiles-daemon backend code used to
+ implement the profile.
+
+ The key named "Profile" (s) will be one of:
+ - "power-saver" (battery saving profile)
+ - "balanced" (the default profile)
+ - "performance" (a profile that does not care about noise or battery consumption)
+
+ Only one of each type of profile will be listed, with the daemon choosing the
+ more appropriate "driver" for each profile type.
+ -->
+ <property name="Profiles" type="aa{sv}" access="read"/>
+
+ <!--
+ Actions:
+
+ An array of strings listing each one of the "actions" implemented in
+ the running daemon. This is used by API users to figure out whether
+ particular functionality is available in a version of the daemon.
+ -->
+ <property name="Actions" type="as" access="read"/>
+
+ </interface>
+</node>
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/shell/dbus-interfaces">
+ <file preprocess="xml-stripblanks">net.hadess.PowerProfiles.xml</file>
<file preprocess="xml-stripblanks">net.hadess.SensorProxy.xml</file>
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Device.xml</file>
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Manager.xml</file>
<file>ui/status/nightLight.js</file>
<file>ui/status/network.js</file>
<file>ui/status/power.js</file>
+ <file>ui/status/powerProfiles.js</file>
<file>ui/status/rfkill.js</file>
<file>ui/status/volume.js</file>
<file>ui/status/bluetooth.js</file>
this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet();
this._power = new imports.ui.status.power.Indicator();
+ this._powerProfiles = new imports.ui.status.powerProfiles.Indicator();
this._rfkill = new imports.ui.status.rfkill.Indicator();
this._volume = new imports.ui.status.volume.Indicator();
this._brightness = new imports.ui.status.brightness.Indicator();
this._indicators.add_child(this._rfkill);
this._indicators.add_child(this._volume);
this._indicators.add_child(this._power);
+ this._indicators.add_child(this._powerProfiles);
this.menu.addMenuItem(this._volume.menu);
this.menu.addMenuItem(this._brightness.menu);
this.menu.addMenuItem(this._location.menu);
this.menu.addMenuItem(this._rfkill.menu);
this.menu.addMenuItem(this._power.menu);
+ this.menu.addMenuItem(this._powerProfiles.menu);
this.menu.addMenuItem(this._nightLight.menu);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this._system.menu);
menuLayout.addSizeChild(this._location.menu.actor);
menuLayout.addSizeChild(this._rfkill.menu.actor);
menuLayout.addSizeChild(this._power.menu.actor);
+ menuLayout.addSizeChild(this._powerProfiles.menu.actor);
menuLayout.addSizeChild(this._system.menu.actor);
}
});
--- /dev/null
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported Indicator */
+
+const { Gio, GObject } = imports.gi;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const { loadInterfaceXML } = imports.misc.fileUtils;
+
+const BUS_NAME = 'net.hadess.PowerProfiles';
+const OBJECT_PATH = '/net/hadess/PowerProfiles';
+
+const PowerProfilesIface = loadInterfaceXML('net.hadess.PowerProfiles');
+const PowerProfilesProxy = Gio.DBusProxy.makeProxyWrapper(PowerProfilesIface);
+
+const PROFILE_LABELS = {
+ 'performance': _('Performance Mode'),
+ 'balanced': _('Balanced Power'),
+ 'power-saver': _('Power Saver'),
+};
+const PROFILE_ICONS = {
+ 'performance': 'power-profile-performance-symbolic',
+ 'balanced': 'power-profile-balanced-symbolic',
+ 'power-saver': 'power-profile-power-saver-symbolic',
+};
+
+var Indicator = GObject.registerClass(
+class Indicator extends PanelMenu.SystemIndicator {
+ _init() {
+ super._init();
+
+ this._profileItems = new Map();
+ this._updateProfiles = true;
+
+ this._proxy = new PowerProfilesProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
+ (proxy, error) => {
+ if (error) {
+ log(error.message);
+ } else {
+ this._proxy.connect('g-properties-changed',
+ (p, properties) => {
+ const propertyNames = properties.deep_unpack();
+ this._updateProfiles = 'Profiles' in propertyNames;
+ this._sync();
+ });
+ }
+ this._sync();
+ });
+
+ this._item = new PopupMenu.PopupSubMenuMenuItem('', true);
+
+ this._profileSection = new PopupMenu.PopupMenuSection();
+ this._item.menu.addMenuItem(this._profileSection);
+ this._item.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._item.menu.addSettingsAction(_('Power Settings'),
+ 'gnome-power-panel.desktop');
+ this.menu.addMenuItem(this._item);
+
+ Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
+ this._sessionUpdated();
+ this._sync();
+ }
+
+ _sessionUpdated() {
+ const sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
+ this.menu.setSensitive(sensitive);
+ }
+
+ _sync() {
+ this._item.visible = this._proxy.g_name_owner !== null;
+
+ if (!this._item.visible)
+ return;
+
+ if (this._updateProfiles) {
+ this._profileSection.removeAll();
+ this._profileItems.clear();
+
+ const profiles = this._proxy.Profiles
+ .map(p => p.Profile.unpack())
+ .reverse();
+ for (const profile of profiles) {
+ const label = PROFILE_LABELS[profile];
+ if (!label)
+ continue;
+
+ const item = new PopupMenu.PopupMenuItem(label);
+ item.connect('activate',
+ () => (this._proxy.ActiveProfile = profile));
+ this._profileItems.set(profile, item);
+ this._profileSection.addMenuItem(item);
+ }
+ this._updateProfiles = false;
+ }
+
+ for (const [profile, item] of this._profileItems) {
+ item.setOrnament(profile === this._proxy.ActiveProfile
+ ? PopupMenu.Ornament.DOT
+ : PopupMenu.Ornament.NONE);
+ }
+
+ const perfItem = this._profileItems.get('performance');
+ if (perfItem)
+ perfItem.sensitive = this._proxy.PerformanceInhibited === '';
+
+ this._item.label.text = PROFILE_LABELS[this._proxy.ActiveProfile];
+ this._item.icon.icon_name = PROFILE_ICONS[this._proxy.ActiveProfile];
+ }
+});
js/ui/status/network.js
js/ui/status/nightLight.js
js/ui/status/power.js
+js/ui/status/powerProfiles.js
js/ui/status/remoteAccess.js
js/ui/status/rfkill.js
js/ui/status/system.js